package com.ideaplugin.datamanager.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.meta.*;
import cn.hutool.extra.spring.SpringUtil;
import com.google.common.collect.Lists;
import com.ideaplugin.datamanager.common.DimensionTypeColumn;
import com.ideaplugin.datamanager.common.PageUtil;
import com.ideaplugin.datamanager.common.TableColumnTypeEnum;
import com.ideaplugin.datamanager.config.DynamicDataSource;
import com.ideaplugin.datamanager.config.MultiDataSourceProperties;
import com.ideaplugin.datamanager.entity.TableColumn;
import com.ideaplugin.datamanager.entity.TableInfo;
import com.ideaplugin.datamanager.entity.table.TableAuthTableDef;
import com.ideaplugin.datamanager.entity.table.TableColumnAuthTableDef;
import com.ideaplugin.datamanager.entity.table.TableColumnTableDef;
import com.ideaplugin.datamanager.entity.table.TableColumnUserCustomizedTableDef;
import com.ideaplugin.datamanager.manager.*;
import com.ideaplugin.datamanager.model.common.CommonResultCode;
import com.ideaplugin.datamanager.model.common.DwhException;
import com.ideaplugin.datamanager.model.common.PageRequest;
import com.ideaplugin.datamanager.model.common.PageResult;
import com.ideaplugin.datamanager.model.request.ListTablesBySchemaNameParam;
import com.ideaplugin.datamanager.model.request.TableInfoParam;
import com.ideaplugin.datamanager.model.response.DataSourceInfoVo;
import com.ideaplugin.datamanager.model.response.SchemaTableInfoVo;
import com.ideaplugin.datamanager.model.response.TableInfoVo;
import com.ideaplugin.datamanager.service.TableInfoService;
import com.mybatisflex.core.paginate.Page;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.ideaplugin.datamanager.entity.table.TableInfoTableDef.TABLE_INFO;


@Slf4j
@Service
public class TableInfoServiceImpl implements TableInfoService {

    @Autowired
    private TableInfoManager tableInfoManager;
    @Autowired
    private TableAuthManager tableAuthManager;
    @Autowired
    private TableColumnAuthManager tableColumnAuthManager;
    @Autowired
    private TableColumnUserCustomizedManager tableColumnUserOrderManager;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private TableColumnManager tableColumnManager;

    @Override
    public PageResult<TableInfoVo> page(PageRequest<TableInfoParam> pageBase) {
        final TableInfoParam query = Objects.requireNonNullElseGet(pageBase.getQuery(), TableInfoParam::new);
        final Page<TableInfoVo> page = tableInfoManager.queryChain()
                .from(TableInfo.class)
                .where(TABLE_INFO.TABLE_NAME.like(query.getName()))
                .where(TABLE_INFO.DATA_SOURCE.eq(query.getDataSource()))
                .orderBy(TABLE_INFO.CREATE_TIME, false)
                .pageAs(PageUtil.convert(pageBase), TableInfoVo.class);
        return PageUtil.convert(page);
    }

    @Override
    public boolean saveOrUpdate(TableInfoParam param) {
        final TableInfo tableInfo = BeanUtil.copyProperties(param, TableInfo.class);
        final boolean saveOrUpdate = tableInfoManager.saveOrUpdate(tableInfo);
        // add
        if (Objects.isNull(param.getId()) && param.getAddType() == TableInfoParam.AddType.auto_import) {
            this.importByTableName(param.getTableName(), tableInfo.getId());
        }
        return saveOrUpdate;
    }

    @Override
    public boolean delete(Long id) {
        tableInfoManager.removeById(id);

        tableColumnManager.remove(TableColumnTableDef.TABLE_COLUMN.TABLE_INFO_ID.eq(id));

        tableAuthManager.remove(TableAuthTableDef.TABLE_AUTH.TABLE_INFO_ID.eq(id));

        tableColumnAuthManager.remove(TableColumnAuthTableDef.TABLE_COLUMN_AUTH.TABLE_INFO_ID.eq(id));

        tableColumnUserOrderManager.remove(TableColumnUserCustomizedTableDef.TABLE_COLUMN_USER_CUSTOMIZED.TABLE_INFO_ID.eq(id));
        return true;
    }

    @Override
    public boolean importByTableName(String fullTableName, Long tableInfoId) {
        final String[] split = fullTableName.split("\\.");
        final String schemaName = split[0];
        final String tableName = split[1];
        final Table tableMeta = MetaUtil.getTableMeta(dataSource, null, schemaName, tableName);
        this.saveTableColumnInfo(tableMeta.getColumns(), tableMeta.getIndexInfoList(), tableInfoId);
        return true;
    }

    private void saveTableColumnInfo(Collection<Column> columns, List<IndexInfo> indexInfoList, Long tableInfoId) {

        final Map<String, Column> columnsInfo = columns.stream().collect(Collectors.toMap(Column::getName, Function.identity()));
        final ArrayList<@Nullable TableColumn> tableColumns = Lists.newArrayList();
        int order = 1;
        for (IndexInfo indexInfo : indexInfoList) {
            final List<ColumnIndexInfo> columnIndexInfoList = indexInfo.getColumnIndexInfoList();
            for (ColumnIndexInfo columnIndexInfo : columnIndexInfoList) {
                final String columnCode = columnIndexInfo.getColumnName();
                TableColumn tableColumn = new TableColumn();
                tableColumn.setTableInfoId(tableInfoId);
                tableColumn.setColumnCode(columnCode);
                final Column column = columnsInfo.remove(columnCode);
                tableColumn.setColumnName(getColumnName(columnCode, column.getComment()));
                tableColumn.setColumnType(TableColumnTypeEnum.getColumnType(column.getType()).name());
                tableColumn.setDimensionType(DimensionTypeColumn.dimension.name());
                if (column.isPk()) {
                    tableColumn.setDimensionExtra(DimensionTypeColumn.primary_key.name());
                } else {
                    tableColumn.setDimensionExtra("normal");
                }
                tableColumn.setOrderNo(order++);
                tableColumns.add(tableColumn);
            }
        }

        for (Column column : columnsInfo.values()) {
            final String columnCode = column.getName();
            TableColumn tableColumn = new TableColumn();
            tableColumn.setTableInfoId(tableInfoId);
            tableColumn.setColumnCode(columnCode);
            final String remarks = column.getComment();
            tableColumn.setColumnName(getColumnName(columnCode, remarks));
            tableColumn.setDimensionType(DimensionTypeColumn.indicators.name());
            final TableColumnTypeEnum columnType = TableColumnTypeEnum.getColumnType(column.getType());
            tableColumn.setColumnType(columnType.name());
            if (columnType == TableColumnTypeEnum.date) {
                tableColumn.setDimensionExtra("date");
            } else {
                tableColumn.setDimensionExtra("input");
            }
            tableColumn.setOrderNo(order++);
            tableColumns.add(tableColumn);
        }
        tableColumnManager.saveBatch(tableColumns);
    }

    private String getColumnName(String columnCode, String remarks) {
        if (StrUtil.length(remarks) > 20) {
            return StrUtil.sub(remarks, 0, 20);
        }
        if (StrUtil.isEmpty(remarks)) {
            return columnCode;
        }
        return remarks;
    }


    @Override
    public List<SchemaTableInfoVo> listTablesBySchemaName(ListTablesBySchemaNameParam param) {
        return DynamicDataSource.setDataSourceAndExecute(param.getDataSourceName(), () -> {
            final DataSource dataSource = SpringUtil.getBean(DataSource.class);
            try (Connection connection = dataSource.getConnection()) {
                final ResultSet catalogs = connection.getMetaData().getTables(param.getSchemaName(), null, null, new String[]{TableType.TABLE.value()});
                List<SchemaTableInfoVo> result = new ArrayList<>();
                while (catalogs.next()) {
                    SchemaTableInfoVo vo = new SchemaTableInfoVo();
                    final String tableName = catalogs.getString("TABLE_NAME");
                    vo.setCode(tableName);
                    String remark = catalogs.getString("REMARKS");
                    if (StrUtil.isEmpty(remark)) {
                        remark = tableName;
                    }
                    vo.setName(remark);
                    result.add(vo);
                }
                return result;
            } catch (SQLException e) {
                throw new DwhException(CommonResultCode.EXCEPTION.code, "查Schema下面表名异常");
            }
        });
    }

    @Override
    public List<String> listSchemaByDataSource(String dataSourceName) {
        return DynamicDataSource.setDataSourceAndExecute(dataSourceName, () -> {
            final DataSource dataSource = SpringUtil.getBean(DataSource.class);
            try (Connection connection = dataSource.getConnection()) {
                final ResultSet catalogs = connection.getMetaData().getCatalogs();
                List<String> result = new ArrayList<>();
                while (catalogs.next()) {
                    result.add(catalogs.getString(1));
                }
                return result;
            } catch (SQLException e) {
                throw new DwhException(CommonResultCode.EXCEPTION.code, "查数据源下面的所有库名异常");
            }
        });
    }

    @Override
    public List<DataSourceInfoVo> listDataSource() {
        final DataSource dataSource = SpringUtil.getBean(DataSource.class);
        final Environment environment = SpringUtil.getBean(Environment.class);
        final String property = environment.getProperty("spring.datasource.name");
        List<DataSourceInfoVo> result = new ArrayList<>();
        if (StrUtil.isNotBlank(property)) {
            result.add(new DataSourceInfoVo(property, property));
        } else {
            result.add(new DataSourceInfoVo(dataSource.toString(), dataSource.toString()));
        }
        if (dataSource instanceof DynamicDataSource) {
            final MultiDataSourceProperties bean = SpringUtil.getBean(MultiDataSourceProperties.class);
            final DataSourceProperties[] extensionDataSources = bean.getExtensionDataSources();
            if (extensionDataSources != null && extensionDataSources.length > 0) {
                for (int i = 0; i < extensionDataSources.length; i++) {
                    final DataSourceProperties dataSourceProperties = extensionDataSources[i];
                    String dataDsName = "slave" + i;
                    if (StrUtil.isNotBlank(dataSourceProperties.getName())) {
                        dataDsName = dataSourceProperties.getName();
                    }
                    result.add(new DataSourceInfoVo(dataDsName, dataDsName));
                }
            }
            return result;
        }

        return result;

    }


    @Override
    public TableInfoVo getById(Long id) {
        final TableInfo tableInfo = tableInfoManager.getById(id);
        TableInfoVo result = new TableInfoVo();
        BeanUtils.copyProperties(tableInfo, result);
        return result;
    }
}
